package com.winit.schedule.job.tracker.repository.mysql.plugin;

import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Mybatis - 通用分页拦截器 Created by ye.liu on 2014/8/08.
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = {
		MappedStatement.class, Object.class, RowBounds.class,
		ResultHandler.class }) })
public class MyBatisPaginationPlugin implements Interceptor {

	private static Logger logger = LoggerFactory
			.getLogger(MyBatisPaginationPlugin.class);

	// 数据库方言
	private String dialect = "oracle";

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object[] args = invocation.getArgs();
		Object obj = args[1];
		if (null != obj && obj instanceof SearchRequest) {
			MappedStatement ms = (MappedStatement) args[0];
			SearchRequest<?> searchRequest = (SearchRequest<?>) obj;

			invocation.getArgs()[1] = searchRequest.getParameterEntity();

			BoundSql boundSql = ms.getBoundSql(searchRequest
					.getParameterEntity());
			// 原始sql
			String searchableSql = boundSql.getSql();

			// 数据分页sql
			String pageSql = getPageSql(searchableSql, searchRequest);
			BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(),
					pageSql, boundSql.getParameterMappings(),
					boundSql.getParameterObject());

			MappedStatement pageMappend = copyFromMappedStatement(ms,
					new BoundSqlSqlSource(pageBoundSql));

			// 设置新的分页sql语句
			invocation.getArgs()[0] = pageMappend;

			// 注销内存分页
			args[2] = RowBounds.DEFAULT;

			return invocation.proceed();
		}
		return invocation.proceed();
	}

	/**
	 * sqlSource
	 * 
	 * @author fu.wan
	 */
	public static class BoundSqlSqlSource implements SqlSource {

		BoundSql boundSql;

		public BoundSqlSqlSource(BoundSql boundSql) {
			this.boundSql = boundSql;
		}

		public BoundSql getBoundSql(Object parameterObject) {
			return boundSql;
		}
	}

	private MappedStatement copyFromMappedStatement(MappedStatement ms,
			SqlSource newSqlSource) {
		Builder builder = new Builder(ms.getConfiguration(), ms.getId(),
				newSqlSource, ms.getSqlCommandType());

		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
			StringBuffer keyProperties = new StringBuffer();
			for (String keyProperty : ms.getKeyProperties()) {
				keyProperties.append(keyProperty).append(",");
			}
			keyProperties.delete(keyProperties.length() - 1,
					keyProperties.length());
			builder.keyProperty(keyProperties.toString());
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.resultSetType(ms.getResultSetType());
		builder.cache(ms.getCache());
		builder.flushCacheRequired(ms.isFlushCacheRequired());
		builder.useCache(ms.isUseCache());
		return builder.build();
	}

	/**
	 * 获取分页sql - 支持Oracle,Mysql,hsqldb
	 * 
	 * @param sql
	 * @param request
	 * @return
	 */
	private String getPageSql(String sql, SearchRequest<?> request) {
		StringBuilder pageSql = new StringBuilder(200);
		if ("mysql".equals(dialect)) {
			pageSql.append(sql);
			pageSql.append(" limit " + request.getOffset() + ","
					+ request.getPageSize());
		} else if ("oracle".equals(dialect)) {
			pageSql.append("select * from ( select temp.*, rownum row_id from ( ");
			pageSql.append(sql);
			pageSql.append(" ) temp where rownum <= ").append(
					request.getOffset() + request.getPageSize());
			pageSql.append(") where row_id > ").append(request.getOffset());
		} else if ("hsqldb".equals(dialect)) {
			pageSql.append(sql);
			pageSql.append(" LIMIT " + request.getOffset() + " OFFSET "
					+ request.getPageSize());
		} else if ("h2db".equals(dialect)) {
			pageSql.append("select top " + request.getPageSize() + " * from ( ");
			pageSql.append(sql);
			pageSql.append(" ) interceptorObject where interceptorObject.id not in (");
			pageSql.append("select top " + request.getOffset()
					+ " interceptorPage.id from ( " + sql
					+ " ) interceptorPage )");
		}
		return pageSql.toString();
	}

	/**
	 * 只拦截StatementHandler
	 * 
	 * @param target
	 * @return
	 */
	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler || target instanceof Executor) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties p) {
		String dialectProp = p.getProperty("dialect");
		String dialects = "oracle,mysql,h2db,hsqldb";
		if (dialectProp != null && dialects.indexOf(dialectProp) >= 0) {
			this.dialect = dialectProp;
		} else {
			logger.error("the dialect property '" + dialectProp
					+ "' not be availabled,default dialect is 'oracle'");
		}
	}
}
